package ui;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.LineBorder;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import kernal.WorkRunnable;

import util.IEventCallback;
import util.SystemMonitor;

public class JImageResizerApp extends JFrame implements ActionListener {

	class ResourceInfo {
		private Integer id;
		private Long maxMemoryUsed;
		private Long avgMemoryUsed;
		private Double totalExecTime;
		private Double avgCPUUsage;

		public Integer id() {
			return id;
		}

		public Double totalExecTime() {
			return totalExecTime;
		}

		public Long maxMemoryUsed() {
			return maxMemoryUsed;
		}

		public Long avgMemoryUsed() {
			return avgMemoryUsed;
		}

		public Double avgCPUUsage() {
			return avgCPUUsage;
		}

		public ResourceInfo() {
			this.id = -1;
			this.maxMemoryUsed = 0L;
			this.avgMemoryUsed = 0L;
			this.totalExecTime = 0d;
			this.avgCPUUsage = 0d;
		}

		public ResourceInfo(ResourceInfo r) {
			this.id = r.id;
			this.maxMemoryUsed = r.maxMemoryUsed;
			this.avgMemoryUsed = r.avgMemoryUsed;
			this.totalExecTime = r.totalExecTime;
			this.avgCPUUsage = r.avgCPUUsage;
		}
	}

	class ResourceSortByTime implements Comparator<ResourceInfo> {
		public int compare(ResourceInfo o1, ResourceInfo o2) {
			return o1.totalExecTime().compareTo(o2.totalExecTime());
		}
	}

	class ResourceSortByAvgMemory implements Comparator<ResourceInfo> {
		public int compare(ResourceInfo o1, ResourceInfo o2) {
			return o1.avgMemoryUsed().compareTo(o2.avgMemoryUsed());
		}
	}

	class ResourceSortByMaxMemory implements Comparator<ResourceInfo> {
		public int compare(ResourceInfo o1, ResourceInfo o2) {
			return o1.maxMemoryUsed().compareTo(o2.maxMemoryUsed());
		}
	}

	class ResourceSortByAll implements Comparator<ResourceInfo> {
		public int compare(ResourceInfo o1, ResourceInfo o2) {
			Integer t1 = new Integer(o1.totalExecTime().intValue());
			Integer t2 = new Integer(o2.totalExecTime().intValue());
			int lastCmp = t1.compareTo(t2);
			return (lastCmp != 0 ? lastCmp : o1.avgMemoryUsed().compareTo(o2.avgMemoryUsed()));
		}
	}

	final private static String MSG_LINE = "================================================================";
	final private static String CMD_CHOOSE_SRC = "SELECT_SRC_DIR";
	final private static String CMD_CHOOSE_DST = "SELECT_DST_DIR";
	final private static String CMD_START = "START";
	final private static String NEW_LINE = "\n";
	final private static String MEMORY_INIT = "--K";
	final private static String FILE_SEPARATER = "\\";
	final private static String COL_SINGLE_TAB = "\t";
	final private static String COL_HEADER_TIME = "Time";
	final private static String COL_HEADER_THREAD_COUNT = "Count";
	final private static String COL_HEADER_MEMORY_AVG = "MEM(Avg.)";
	final private static String COL_HEADER_MEMORY_MAX = "MEM(Max.)";
	final private static String COL_HEADER_CPU_AVG = "CPU(Avg.)";
	final private static String MSG_INFO = "[INFO]:";
	final private static String MSG_REPORT = "[REPORT]:";
	final private static String MSG_CALLBACK = "[CALLBACK]:";

	final private static String SORT_BY_TIME = "Sort By Execute Time";
	final private static String SORT_BY_MEM_AVG = "Sort By Average Memory Used";
	final private static String SORT_BY_MEM_MAX = "Sort By Maximum Memory Used";
	final private static String SORT_BY_OPTIMIZE = "Sort By 1.Execute Time 2.Average Memory";

	final private static String DATE_FORMAT_NOW = "yyyyMMdd_HHmmss";
	final private static String MSG_DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";

	private static String imgSrcDir;
	private static String imgOutDir;
	private static ArrayList<String> imgList;
	private static int maxThreads;
	private ArrayList<ResourceInfo> summaryList;
	private ArrayList<WorkRunnable> workList;

	// UI Component
	private JTextField txSourceDir;
	private JTextField txOutputDir;
	private JTextField txThreadCount;
	private JButton btnChooseDst;
	private JButton btnChooseSrc;
	private JButton btnStart;
	private JTextArea taLog;
	private JProgressBar progress;
	private JLabel labMemory;
	private JCheckBox cbSingle;
	private JCheckBox cbMaximum;
	private JCheckBox cbProcessors;
	private JCheckBox cbDefine;

	private FileWriter output;

	private ExecutorService executor;

	public JImageResizerApp() {
		super("Image Resizer");
		// initial UI
		initUIComponents();
	}

	private void initUIComponents() {
		JPanel panel = new JPanel();
		getContentPane().add(panel, BorderLayout.CENTER);
		;
		panel.setLayout(new BorderLayout());

		JPanel panelSetting = new JPanel();
		panel.add(panelSetting, BorderLayout.NORTH);
		panelSetting.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();

		JLabel labSrc = new JLabel("Source Dir:");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 0;
		c.gridy = 0;
		c.gridwidth = 1;
		c.insets = new Insets(2, 2, 2, 2);
		panelSetting.add(labSrc, c);

		txSourceDir = new JTextField("C:\\image_temp\\");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridwidth = 3;
		c.gridx = 1;
		c.gridy = 0;
		panelSetting.add(txSourceDir, c);

		btnChooseSrc = new JButton("Browse");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 4;
		c.gridy = 0;
		c.gridwidth = 1;
		btnChooseSrc.addActionListener(this);
		btnChooseSrc.setActionCommand(CMD_CHOOSE_SRC);
		panelSetting.add(btnChooseSrc, c);

		JLabel labDst = new JLabel("Output Dir:");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 0;
		c.gridy = 1;
		c.gridwidth = 1;
		panelSetting.add(labDst, c);

		txOutputDir = new JTextField("C:\\output\\");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 1;
		c.gridy = 1;
		c.gridwidth = 3;
		panelSetting.add(txOutputDir, c);

		btnChooseDst = new JButton("Browse");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 4;
		c.gridy = 1;
		c.gridwidth = 1;
		btnChooseDst.addActionListener(this);
		btnChooseDst.setActionCommand(CMD_CHOOSE_DST);
		panelSetting.add(btnChooseDst, c);

		cbSingle = new JCheckBox("Single");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(2, 2, 2, 2);
		c.weightx = 0.5;
		c.gridx = 0;
		c.gridy = 2;
		c.gridwidth = 1;
		panelSetting.add(cbSingle, c);

		cbMaximum = new JCheckBox("Maximum");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(2, 2, 2, 2);
		c.weightx = 0.5;
		c.gridx = 1;
		c.gridy = 2;
		c.gridwidth = 1;
		panelSetting.add(cbMaximum, c);

		cbProcessors = new JCheckBox("Processors");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(2, 2, 2, 2);
		c.weightx = 0.5;
		c.gridx = 2;
		c.gridy = 2;
		c.gridwidth = 1;
		panelSetting.add(cbProcessors, c);

		cbDefine = new JCheckBox("User Define");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(2, 2, 2, 2);
		c.weightx = 0.5;
		c.gridx = 3;
		c.gridy = 2;
		c.gridwidth = 1;
		panelSetting.add(cbDefine, c);

		txThreadCount = new JTextField("10");
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(5, 5, 5, 5);
		c.weightx = 0.5;
		c.gridx = 4;
		c.gridwidth = 1;
		c.gridy = 2;
		panelSetting.add(txThreadCount, c);

		JLabel labCPU = new JLabel("CPU Usage:");
		c.insets = new Insets(5, 5, 5, 5);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 0;
		c.gridy = 3;
		c.gridwidth = 1;
		panelSetting.add(labCPU, c);

		progress = new JProgressBar();
		progress.setValue(0);
		progress.setStringPainted(true);
		c.insets = new Insets(5, 5, 5, 5);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 1;
		c.gridy = 3;
		c.gridwidth = 1;
		panelSetting.add(progress, c);

		JLabel labMemoryText = new JLabel("Memory Used:");
		c.insets = new Insets(5, 5, 5, 5);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 2;
		c.gridy = 3;
		c.gridwidth = 1;
		panelSetting.add(labMemoryText, c);

		labMemory = new JLabel(MEMORY_INIT);
		c.insets = new Insets(5, 5, 5, 5);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 3;
		c.gridy = 3;
		c.gridwidth = 1;
		labMemory.setHorizontalTextPosition(JLabel.CENTER);
		labMemory.setVerticalTextPosition(JLabel.CENTER);
		labMemory.setBorder(LineBorder.createGrayLineBorder());
		panelSetting.add(labMemory, c);

		btnStart = new JButton("Start");
		c.insets = new Insets(5, 5, 5, 5);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 4;
		c.gridy = 3;
		c.gridwidth = 1;
		panelSetting.add(btnStart, c);
		btnStart.addActionListener(this);
		btnStart.setActionCommand(CMD_START);

		// log panel
		taLog = new JTextArea(5, 20);
		taLog.setMargin(new Insets(5, 5, 5, 5));
		taLog.setEditable(false);
		JScrollPane logScrollPane = new JScrollPane(taLog);
		panel.add(logScrollPane, BorderLayout.CENTER);
	}

	/**
	 * Create the GUI and show it. For thread safety, this method should be
	 * invoked from the event dispatch thread.
	 */
	private static void createAndShowGUI() {
		// Create and set up the window.
		JFrame frame = new JImageResizerApp();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		// Add content to the window
		frame.pack();
		frame.setLocation(200, 200);
		frame.setSize(800, 450);
		frame.setVisible(true);

	}

	public static void main(String args[]) {
		// Schedule a job for the event dispatch thread:
		// creating and showing this application's GUI.
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				// Turn off metal's use of bold fonts
				UIManager.put("swing.boldMetal", Boolean.TRUE);
				createAndShowGUI();
			}
		});
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equalsIgnoreCase(CMD_CHOOSE_DST)) {
			JFileChooser fc = new JFileChooser(this.txOutputDir.getText());
			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			int returnVal = fc.showOpenDialog(this);

			if (returnVal == JFileChooser.APPROVE_OPTION) {
				File file = fc.getSelectedFile();
				if (file.isFile())
					txOutputDir.setText(file.getParent());
				else
					txOutputDir.setText(file.getAbsolutePath());
				dumpInfoMsg(e.getActionCommand() + ":" + txOutputDir.getText());

			} else {
				dumpInfoMsg(e.getActionCommand() + " cancelled");

			}
		} else if (e.getActionCommand().equalsIgnoreCase(CMD_CHOOSE_SRC)) {
			JFileChooser fc = new JFileChooser(this.txSourceDir.getText());
			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			int returnVal = fc.showOpenDialog(this);

			if (returnVal == JFileChooser.APPROVE_OPTION) {
				File file = fc.getSelectedFile();
				if (file.isFile())
					txSourceDir.setText(file.getParent());
				else
					txSourceDir.setText(file.getAbsolutePath());

				dumpInfoMsg(e.getActionCommand() + ":" + txSourceDir.getText());
			} else {
				dumpInfoMsg(e.getActionCommand() + " cancelled");
			}
		}

		else if (e.getActionCommand().equalsIgnoreCase(CMD_START)) {
			dumpInfoMsg(e.getActionCommand());
			imgSrcDir = txSourceDir.getText() + FILE_SEPARATER;
			imgOutDir = txOutputDir.getText() + FILE_SEPARATER;
			maxThreads = Integer.valueOf(txThreadCount.getText()).intValue();
			new Thread(new Runnable() {
				public void run() {
					try {
						onRunning();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}

	public void onRunning() throws Exception {

		onBeginRunning();

		if (cbSingle.isSelected())
			onSingleThread();

		if (cbMaximum.isSelected())
			onMaximumThread();

		if (cbProcessors.isSelected())
			onProcessorsThread();

		if (cbDefine.isSelected()) {
			/*
			 * Thread t = new Thread(new Runnable() { public void run() { try {
			 * executeThread(maxThreads, 0 , 100); } catch (Exception e) {
			 * e.printStackTrace(); } }}); t.start(); t.join(); t = null;
			 * 
			 * Thread t2 = new Thread(new Runnable() { public void run() { try {
			 * executeThread(maxThreads, 100 , 200); } catch (Exception e) {
			 * e.printStackTrace(); } }}); t2.start(); t2.join(); t2 = null;
			 * 
			 * Thread t3 = new Thread(new Runnable() { public void run() { try {
			 * executeThread(maxThreads, 200 , 350); } catch (Exception e) {
			 * e.printStackTrace(); } }}); t3.start(); t3.join(); t3 = null;
			 */

			onDefineThread();
		}

		onEndRunning();

	}

	public void resetUI() {
		progress.setValue(0);
		labMemory.setText(MEMORY_INIT);
	}

	protected void onSingleThread() throws Exception {
		dumpInfoMsg("onSingleThread");
		executeThread(1);
	}

	protected void onMaximumThread() throws Exception {
		dumpInfoMsg("onMaximumThread");
		executeThread(imgList.size());
	}

	protected void onProcessorsThread() throws Exception {
		dumpInfoMsg("onProcessorsThread");
		executeThread(Runtime.getRuntime().availableProcessors());
	}

	protected void onDefineThread() throws Exception {
		dumpInfoMsg("onDefineThread");
		executeThread(maxThreads);
	}

	private void executeThread(int nThreads) throws Exception {
		dumpInfoMsg(MSG_LINE);
		dumpReportMsg("Start Time:" + now(MSG_DATE_FORMAT_NOW) + " imageCount:" + imgList.size());
		dumpInfoMsg("Execute thread number: " + nThreads);
		SystemMonitor tt = new SystemMonitor(1000); // 100ms interval
		tt.setCallback(this);
		tt.start();
		executor = Executors.newFixedThreadPool(nThreads);
		long start_time = System.currentTimeMillis();
		double total_exec_time = 0.0;
		try {
			File outDir = new File(imgOutDir + now(DATE_FORMAT_NOW));
			outDir.mkdir();
			for (int i = 0; i < imgList.size(); i++) {
				String filepath = (String) imgList.get(i);
				String dstpath = outDir.getAbsolutePath() + FILE_SEPARATER + String.valueOf(i) + ".jpg";
				executor.execute(new WorkRunnable(filepath, dstpath));
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());

		} finally {
			// This will make the executor accept no new threads
			// and finish all existing threads in the queue
			executor.shutdown();
			// Wait until all threads are finish

			while (!executor.isTerminated()) {
				// System.out.println("wait for executor.isTerminated == true!!");
			}
		}

		tt.stopRequest();
		total_exec_time = (System.currentTimeMillis() - start_time) / Double.valueOf(1000);
		ResourceInfo resource = new ResourceInfo();
		resource.id = nThreads;
		resource.avgMemoryUsed = tt.getAvgMemroyUsed();
		resource.maxMemoryUsed = tt.getMaxMemoryUsed();
		resource.avgCPUUsage = tt.getAvgCPUUsage();
		resource.totalExecTime = total_exec_time;

		dumpInfoMsg("Execute total time:" + String.valueOf(resource.totalExecTime) + " sec.");
		dumpInfoMsg("Maxmimum memory used: " + resource.maxMemoryUsed + " kbytes.");
		dumpInfoMsg("Average memory used: " + resource.avgMemoryUsed + " kbytes.");
		dumpInfoMsg("Average CPU usage: " + resource.avgCPUUsage + "%");

		summaryList.add(resource);

		tt = null;
		resource = null;
		dumpReportMsg("End Time:" + now(MSG_DATE_FORMAT_NOW));
		dumpInfoMsg(MSG_LINE + NEW_LINE);
	}

	protected void onBeginRunning() {
		try {
			output = new FileWriter("summary.log", true);
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		imgList = new ArrayList<String>();
		getImageFileList(imgSrcDir, imgList);
		int imgCount = imgList.size();
		dumpInfoMsg("Do image resizing start... total image count:[" + imgCount + "]");
		summaryList = new ArrayList<ResourceInfo>();
	}

	protected void onEndRunning() {
		summary();
		resetUI();
		imgList.clear();
		imgList = null;
		summaryList.clear();
		summaryList = null;
		try {
			output.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	private void summary() {
		// 1. Find the shorter execute time
		/*
		 * Collections.sort(summaryList, new ResourceSortByTime());
		 * dumpResourceMsg(summaryList, SORT_BY_TIME);
		 * 
		 * // 2. Find the minimum avg memory used Collections.sort(summaryList,
		 * new ResourceSortByAvgMemory()); dumpResourceMsg(summaryList,
		 * SORT_BY_MEM_AVG);
		 * 
		 * // 3. Find the minimum max memory used Collections.sort(summaryList,
		 * new ResourceSortByMaxMemory()); dumpResourceMsg(summaryList,
		 * SORT_BY_MEM_MAX);
		 */
		// 4 . Find the optimize solution
		Collections.sort(summaryList, new ResourceSortByAll());
		dumpResourceMsg(summaryList, SORT_BY_OPTIMIZE);

	}

	private void getImageFileList(String basedir, ArrayList<String> filefullpathlist) {
		File file = new File(basedir);
		String filefullpath = "";
		if (file.isFile()) {
			filefullpath = basedir + file.getName();
			dumpInfoMsg("filefullpath=" + filefullpath);
			filefullpathlist.add(filefullpath);
		} else if (file.isDirectory()) {
			File[] ls = file.listFiles();
			for (File f : ls) {
				if (f.isFile() && !f.isHidden()) {
					String name = f.getName();
					int pos = name.lastIndexOf(".");
					if (name.substring(pos).equalsIgnoreCase(".jpg")) {
						filefullpath = basedir + f.getName();
						filefullpathlist.add(filefullpath);
					}
				} else if (f.isDirectory()) {
					filefullpath = basedir + f.getName() + "/";
					getImageFileList(filefullpath, filefullpathlist);
				}
			}
		} else {
			dumpInfoMsg("[getImageFileList] getImageFileList is wrong!");
		}
	}

	public static String now(String format) {
		Calendar cal = Calendar.getInstance();
		SimpleDateFormat sdf = new SimpleDateFormat(format);
		return sdf.format(cal.getTime());
	}

	protected void dumpInfoMsg(String msg) {
		dumpDefineMsg(MSG_INFO, msg);
	}

	protected void dumpCallbackMsg(String msg) {
		dumpDefineMsg(MSG_CALLBACK, msg);
	}

	protected void dumpReportMsg(String msg) {
		try {
			output.append(MSG_REPORT + msg + NEW_LINE);
		} catch (IOException e) {
			e.printStackTrace();
		}
		dumpDefineMsg(MSG_REPORT, msg);
	}

	protected void dumpDefineMsg(String header, String msg) {
		msg = header + msg;
		dumpMsg(msg);
	}

	protected void dumpMemoryMsg(String header) {
		long total = Runtime.getRuntime().totalMemory();
		long free = Runtime.getRuntime().freeMemory();
		long used = total - free;

		dumpDefineMsg(header, "Total Mem:" + total / 1024 + "K.");
		dumpDefineMsg(header, "Free Mem:" + free / 1024 + "K.");
		dumpDefineMsg(header, "Used Mem:" + used / 1024 + "K.");
	}

	protected void dumpResourceMsg(ArrayList<ResourceInfo> ary, String title) {
		dumpReportMsg(getReportHeader(title));
		for (ResourceInfo r : ary) {
			String msg = COL_SINGLE_TAB + r.id() + COL_SINGLE_TAB + r.totalExecTime() + COL_SINGLE_TAB
					+ r.avgMemoryUsed() + COL_SINGLE_TAB + r.maxMemoryUsed() + COL_SINGLE_TAB + r.avgCPUUsage();
			dumpReportMsg(msg);
		}
	}

	private void dumpMsg(String msg) {
		System.out.println(msg);
		taLog.append(msg + NEW_LINE);
		taLog.setCaretPosition(taLog.getDocument().getLength());
	}

	public void callback(long evt, String info) throws Exception {
		StringBuffer msg = new StringBuffer();
		if (evt == IEventCallback.EVT_CPU_USAGE) {
			msg.append("[CPU Usage]: " + info + "%");
			progress.setValue((int) Integer.valueOf(info));
		} else if (evt == IEventCallback.EVT_MEMORY_USAGE) {
			msg.append("[Mem Usage]: " + info + "K");
			labMemory.setText(info + "K");
		} else if (evt == IEventCallback.EVT_ERROR) {

		}
		// dumpCallbackMsg(msg.toString());
	}

	private String getReportHeader(String title) {
		StringBuffer sb = new StringBuffer();
		sb.append(MSG_LINE);
		sb.append(NEW_LINE);
		sb.append(MSG_REPORT);
		sb.append(title);
		sb.append(NEW_LINE);
		sb.append(MSG_REPORT);
		sb.append(MSG_LINE);
		sb.append(NEW_LINE);
		sb.append(MSG_REPORT);
		sb.append(COL_SINGLE_TAB);
		sb.append(COL_HEADER_THREAD_COUNT);
		sb.append(COL_SINGLE_TAB);
		sb.append(COL_HEADER_TIME);
		sb.append(COL_SINGLE_TAB);
		sb.append(COL_HEADER_MEMORY_AVG);
		sb.append(COL_SINGLE_TAB);
		sb.append(COL_HEADER_MEMORY_MAX);
		sb.append(COL_SINGLE_TAB);
		sb.append(COL_HEADER_CPU_AVG);
		return sb.toString();
	}
}
